package archer.framework.security.apitenant.filter;

import static archer.framework.protocol.ArcherProtocolModule.*;

import archer.framework.core.constant.Constant;
import archer.framework.protocol.result.ExecuteResultProcessor;
import archer.framework.security.apitenant.ApiTenant;
import archer.framework.security.apitenant.ApiTenantDao;
import archer.framework.security.netpolice.SuspiciousEvent;
import archer.framework.security.utils.ApiTenantContext;
import archer.framework.security.utils.SubjectUtils;
import archer.framework.security.utils.SuspiciousUtils;
import archer.framework.utils.*;
import archer.framework.web.entity.AccessLog;
import jodd.util.Wildcard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;
import java.sql.Timestamp;

/**
 * @author christ
 * @date 2016/6/2
 */
public class ApiTenantFilter implements Filter {

    private static final String STATUS_PASS = "PASS";

    private static final String STATUS_FORBIDDEN = "FORBIDDEN";

    private static final Logger logger = LoggerFactory.getLogger(ApiTenantFilter.class);

    /**
     * 请求是否需要签名
     */
    protected String signature = "true";

    protected String checkUserAgent = "true";

    protected String ignorePaths = "";

    protected String[] ignores;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        FilterUtils.readFilterConfigParams(filterConfig, this, "signature", "checkUserAgent", "ignorePaths");

        ignores = ignorePaths.split(";");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        // 准备访问日志对象
        AccessLog log = prepareAccessLog(request);

        // 获取apikey字段值
        String apikey = getApikey(request);

        // 跳过检查
        if (ignore(request)) {
            logAccessPass(log.addAttr("status", STATUS_PASS).addAttr("description", "跳过检查"));
            chain.doFilter(request, response);
        }

        // 校验apikey长度
        if (ValidateUtils.isEmpty(apikey) || apikey.length() < 20) {

            logAccessForbidden(handleAccessForbiddenLog(log, "apikey长度非法"));

            accessForbidden(request, response);

            return;
        }

        // userAgent判断
        if (Boolean.valueOf(checkUserAgent) && ProfileUtils.isProductionProfile()
                && !userAgent.equals(ServletUtils.getRequest(request).getHeader("User-Agent"))) {

            logAccessForbidden(handleAccessForbiddenLog(log, "userAgent非法"));

            accessForbidden(request, response);

            return;
        }

        // 根据apikey查找ApiTenant对象
        ApiTenant apiTenant = findApiTenant(apikey);
        if (ValidateUtils.isEmpty(apiTenant)) {

            logAccessForbidden(handleAccessForbiddenLog(log, "apikey错误"));

            accessForbidden(request, response);

            return;
        }

        // 如果是签名模式
        if (Boolean.valueOf(signature)) {

            String signatureValue = getSignature(request);
            String checkSign = SignatureUtils.genSignature(request.getParameterMap(), apiTenant.getApisecret(), signatureField);

            if (ValidateUtils.isNotEmpty(checkSign) && !checkSign.equals(signatureValue)) {

                logAccessForbidden(handleAccessForbiddenLog(log, "signature错误"));

                accessForbidden(request, response);

                return;
            }
        }


        // should pass
        logAccessPass(handleAccessPassLog(log, apiTenant));

        accessPass(apiTenant, request, response, chain);

    }

    protected boolean ignore(ServletRequest servletRequest) {

        if (ValidateUtils.isNotEmpty(ignores)) {
            for (String each : ignores) {
                if (Wildcard.match(ServletUtils.getRequestURI(servletRequest), each)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获得ApiTenant对象
     *
     * @param apikey
     * @return
     */
    protected ApiTenant findApiTenant(String apikey) {

        return ContextUtils.getBean(ApiTenantDao.class).find(apikey);
    }

    /**
     * 拒绝
     *
     * @param response
     * @throws IOException
     * @throws ServletException
     */
    protected void accessForbidden(ServletRequest request, ServletResponse response) throws IOException, ServletException {

        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getOutputStream().write(JSONUtils.serialize(ExecuteResultProcessor.apitenantAccessDenied()).getBytes("UTF-8"));
    }

    /**
     * 放行
     *
     * @param apiTenant
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    protected void accessPass(ApiTenant apiTenant, ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        // 设置apiTenant到线程变量
        ApiTenantContext.setApiTenant(apiTenant);

        ServletUtils.setHeader(response, "Access-Control-Allow-Origin", "*");

        chain.doFilter(request, response);
    }

    protected String getApikey(ServletRequest request) {

        return request.getParameter(apikeyField);
    }

    protected String getSignature(ServletRequest request) {

        return request.getParameter(signatureField);
    }

    protected AccessLog prepareAccessLog(ServletRequest request) {

        return new AccessLog()
                .setName("API CALL")
                .setUrl(ServletUtils.getRequestURI(request))
                .setUserId(SubjectUtils.getCurrentUserId())
                .setParams(RequestParamUtils.getQueryStringWithMask(request.getParameterMap(), "password"))
                .setMethod(ServletUtils.getMethod(request))
                .setUserAgent(ServletUtils.getRequest(request).getHeader("User-Agent"))
                .setAccessDate(new Timestamp(System.currentTimeMillis()))
                .setIp(ServletUtils.getClientIp(request))
                .addAttr("apikey", getApikey(request));
    }

    protected AccessLog handleAccessForbiddenLog(AccessLog log, String description) {

        return log.addAttr("status", STATUS_FORBIDDEN).addAttr("description", description);
    }

    protected AccessLog handleAccessPassLog(AccessLog log, ApiTenant apiTenant) {

        return log.addAttr("status", STATUS_PASS).addAttr("description", "正常").addAttr("tenantName", apiTenant.getName());
    }

    protected void logAccessForbidden(AccessLog log) {

        reportSuspiciousEvent(log);

        logger.error(log.toLogStr());
    }

    /**
     * 上报可疑事件
     *
     * @param log
     */
    protected void reportSuspiciousEvent(AccessLog log) {

        SuspiciousEvent event = new SuspiciousEvent()
                .setTitle("api tenant error")
                .setLevel(Constant.SuspiciousLevel.ORANGE)
                .setType(Constant.SuspiciousType.ILLEGAL_API_TENANT)
                .setUserId(SubjectUtils.getCurrentUserId())
                .setContent(JSONUtils.serialize(log));

        SuspiciousUtils.report(event);
    }

    protected void logAccessPass(AccessLog log) {

        logger.info(log.toLogStr());
    }

    @Override
    public void destroy() {
        // empty
    }
}
